use std::path::Path;

use tracing::debug;

use bc_base::{
    BlockId, TransactionId,
    transaction::{Transaction, TransactionIndexNode, TxIndexCache},
};
use bc_block_data::TransactionAndIndexStorage;

use crate::top_storage::TopStorageError;

#[derive(Debug)]
pub struct TransactionAndIndexStorageWithCache {
    tx_index_cache: TxIndexCache,
    tx_and_index_storage: TransactionAndIndexStorage,
}

impl TransactionAndIndexStorageWithCache {
    pub async fn new(db_root_dir: &Path) -> Self {
        let tx_and_index_storage = TransactionAndIndexStorage::new(db_root_dir).unwrap();
        let mut t = Self {
            tx_index_cache: TxIndexCache::new(),
            tx_and_index_storage,
        };

        bc_util::execute_function_elapsed("load_to_tx_index_cache", async {
            t.tx_and_index_storage
                .load_tx_index_to_cache(&mut t.tx_index_cache)
                .await
                .unwrap();
            debug!("tx_index_cache len: {}", t.tx_index_cache.len());
        })
        .await;

        t
    }

    pub async fn get_item_number(&self) -> usize {
        let n1 = self.tx_index_cache.len();
        #[cfg(debug_assertions)]
        {
            let n2 = self.tx_and_index_storage.get_item_number().await;
            debug_assert_eq!(n1, n2);
        }
        n1
    }

    pub fn iter_tx_index(&self) -> impl Iterator<Item = (&TransactionId, &TransactionIndexNode)> {
        self.tx_index_cache.iter()
    }

    pub async fn write_tx_and_index_sync(
        &mut self,
        tx_id: &TransactionId,
        tx: &Transaction,
        belong_to_block: Option<BlockId>,
    ) -> Result<(), TopStorageError> {
        self.tx_and_index_storage.write_tx_to_db(tx_id, tx).await?;
        let tx_ind = TransactionIndexNode::new_1(tx, belong_to_block);
        self.write_tx_index_node_sync(tx_id, tx_ind).await?;
        Ok(())
    }

    pub async fn delete_tx_and_index_sync(
        &mut self,
        tx_id: &TransactionId,
    ) -> Result<(), TopStorageError> {
        self.tx_and_index_storage.delete_tx_from_db(tx_id).await?;
        self.delete_tx_index_node_sync(tx_id).await?;
        Ok(())
    }

    pub async fn flush_tx_and_index(&self) -> Result<(), TopStorageError> {
        self.tx_and_index_storage.flush_tx_and_index_db().await?;
        Ok(())
    }
}

// for tx
impl TransactionAndIndexStorageWithCache {
    pub async fn tx_exists(&self, tx_id: &TransactionId) -> bool {
        self.tx_and_index_storage.tx_exists_in_db(tx_id).await
    }

    pub async fn read_tx(&self, tx_id: &TransactionId) -> Result<Transaction, TopStorageError> {
        let r = self.tx_and_index_storage.read_tx_from_db(tx_id).await?;
        Ok(r)
    }
}

// for tx index
impl TransactionAndIndexStorageWithCache {
    pub fn tx_index_exists(&self, tx_id: &TransactionId) -> bool {
        self.tx_index_cache.contains_key(tx_id)
    }

    pub async fn read_tx_index_node(
        &self,
        tx_id: &TransactionId,
    ) -> Result<TransactionIndexNode, TopStorageError> {
        if let Some(tx_index) = self.tx_index_cache.get(tx_id) {
            Ok(tx_index.clone())
        } else {
            let tx_index = self
                .tx_and_index_storage
                .read_tx_index_from_db(tx_id)
                .await?;
            Ok(tx_index)
        }
    }

    pub async fn update_tx_index_node_sync(
        &mut self,
        tx_id: &TransactionId,
        tx_ind: TransactionIndexNode,
    ) -> Result<(), TopStorageError> {
        debug_assert!(self.tx_index_cache.contains_key(tx_id));
        debug_assert!(self.tx_and_index_storage.tx_index_exists_in_db(tx_id).await);
        self.tx_and_index_storage
            .write_tx_index_to_db(tx_id, &tx_ind)
            .await?;
        self.tx_index_cache.insert(*tx_id, tx_ind);
        Ok(())
    }

    async fn write_tx_index_node_sync(
        &mut self,
        tx_id: &TransactionId,
        tx_ind: TransactionIndexNode,
    ) -> Result<(), TopStorageError> {
        self.tx_and_index_storage
            .write_tx_index_to_db(tx_id, &tx_ind)
            .await?;
        self.tx_index_cache.insert(*tx_id, tx_ind);

        Ok(())
    }

    async fn delete_tx_index_node_sync(
        &mut self,
        tx_id: &TransactionId,
    ) -> Result<(), TopStorageError> {
        self.tx_and_index_storage
            .delete_tx_index_from_db(tx_id)
            .await?;
        self.tx_index_cache.remove(tx_id);

        Ok(())
    }
}
